package com.me.study.jdk8.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * JDK8：stream.reduce()
 * 单个参数、两个参数的 reduce，类型只能和 stream类型相同。
 * 如果 stream 内的元素需要转换才能使用，则只能用 三参的 reduce
 *
 * @author ME
 * @date   2019/9/19
 */
public class TestReduce {

    public static void main(String[] args) {
        /**
         * 求和，也可以写成Lambda语法：
         * Integer sum = s.reduce((a, b) -> a + b).get();
         */
        System.out.println("\n一参reduce()");
        sum1();

        System.out.println("\n两参reduce()");
        sum2();

        System.out.println("\n三参reduce(): 非并行");
        sum3$1();

        System.out.println("\n三参reduce(): 非并行 模拟 Stream中的 Filter()");
        sum3$2();

        /* 非并行运算结果是: 4 + 1 + 2 + 3 = 10
         * 并行运算结果是: (4+1) + (4+2) + (4+3) = 18
         */
        System.out.println("\n=================三参reduce(): 非并行");
        String[] arr = new String[]{"aa", "bb", "cc", "ff", "gg"};
        Stream<String> stream1 = Arrays.stream(arr);
        sum3$3(stream1);

        System.out.println("\n=================三参reduce(): 并行");
        Stream<String> stream2 = Arrays.stream(arr).parallel();
        sum3$3(stream2);

        System.out.println("\n遍历 list: ['123', '456', '789'], 把每个元素用 new Object[]{s} 进行包装");
        List<String> a = new ArrayList<>();
        a.add("123");
        a.add("456");
        a.add("789");
        ArrayList<Object[]> result = a.stream().reduce(new ArrayList<Object[]>(),
            new BiFunction<ArrayList<Object[]>, String, ArrayList<Object[]>>() {
                @Override
                public ArrayList<Object[]> apply(ArrayList<Object[]> u, String s) {
                    u.add(new Object[]{s});
                    return u;
                }
            }, new BinaryOperator<ArrayList<Object[]>>() {
                @Override
                public ArrayList<Object[]> apply(ArrayList<Object[]> strings, ArrayList<Object[]> strings2) {
                    return strings;
                }
            });
        System.out.println(result);
    }

    /**
     * 一个参数的 reduce, 效果是 a[0] + a[1], 然后 result + a[2] 直到 a[n-1]
     *
     * @return
     */
    public static Integer sum1() {
        Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5, 6);
        Integer sum = s1.reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        }).get();
        System.out.println(sum);
        return sum;
    }

    /**
     * 两个参数的 reduce, 多了个参数是结果的初始值： 效果是 result=identity, 然后 result + a[0] 直到 a[n-1]
     *
     * @return
     */
    public static Integer sum2() {
        Stream<Integer> s2 = Stream.of(1, 2, 3, 4, 5, 6);
        Integer sum = s2.reduce(3, new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer a, Integer b) {
                return Integer.sum(a, b);
            }
        });
        System.out.println(sum);
        return sum;
    }

    /**
     * 三个参数的 reduce,
     * identity: 一个初始化的值；这个初始化的值其类型是泛型 U，与 Reduce方法返回的类型一致；
     *      注意此时 Stream中元素的类型是 T，与 U可以不一样也可以一样，这样的话操作空间就大了；
     *      不管 Stream中存储的元素是什么类型，U都可以是任何类型，如 U可以是一些基本数据类型的包装类型 Integer、Long等；
     *      或者是 String，又或者是一些集合类型 ArrayList等；后面会说到这些用法。
     * accumulator: 累加器，其类型是 BiFunction，输入是 U与 T两个类型的数据，而返回的是U类型；也就是说返回的类型与输入的第一个参数类型是一样的，而输入的第二个参数类型与Stream中元素类型是一样的。
     * combiner: 组合器，其类型是 BinaryOperator，并行计算模式下，对每个元素处理；
     *
     * 第三个参数combiner主要是使用在并行计算的场景下；如果Stream是非并行时，第三个参数实际上是不生效的。
     *
     * 不一样，就存在很多种用法了。如假设U的类型是ArrayList，那么可以将Stream中所有元素添加到ArrayList中再返回
     *
     * @return
     */
    public static ArrayList<String> sum3$1() {
        Stream<String> s3 = Stream.of("aa", "ab", "c", "ad");

        ArrayList<String> arr = s3.reduce(new ArrayList<>(),
            new BiFunction<ArrayList<String>, String, ArrayList<String>>() {
                @Override
                public ArrayList<String> apply(ArrayList<String> u, String s) {
                    u.add(s);
                    return u;
                }
            }, new BinaryOperator<ArrayList<String>>() {
                @Override
                public ArrayList<String> apply(ArrayList<String> strings, ArrayList<String> strings2) {
                    return strings;
                }
            });
        System.out.println(arr);
        return arr;
    }

    /**
     * 三参 非并行，模拟 Stream中的 Filter().
     * lambda语法：
     *   s1.reduce(new ArrayList<String>(), (r, t) -> {if (predicate.test(t)) r.add(t);  return r; },
     * 		(r1, r2) -> r1).stream().forEach(System.out::println);
     *
     * @return
     */
    public static ArrayList<String> sum3$2() {
        Stream<String> s3 = Stream.of("aa", "ab", "c", "ad");

        Predicate<String> predicate = t -> t.contains("a");
        ArrayList<String> arr = s3.reduce(new ArrayList<>(),
            new BiFunction<ArrayList<String>, String, ArrayList<String>>() {
                @Override
                public ArrayList<String> apply(ArrayList<String> strings, String s) {
                    if (predicate.test(s)) strings.add(s);
                    return strings;
                }
            },
            new BinaryOperator<ArrayList<String>>() {
                @Override
                public ArrayList<String> apply(ArrayList<String> strings, ArrayList<String> strings2) {
                    return strings;
                }
            });
        System.out.println(arr);
        // arr.stream().forEach(System.out::println);

        return arr;
    }

    /**
     * 三参 并行用法, 结果跟非并行不一样！！！
     *
     * @return
     */
    public static String sum3$3(Stream<String> stream) {
        String result = stream.reduce("[init]",
            new BiFunction<String, String, String>() {
                @Override
                public String apply(String s1, String s2) {
                    return "1" + s1 + "_" + s2;
                }
            },
            new BinaryOperator<String>() {
                @Override
                public String apply(String s1, String s2) {
                    System.out.println("sum3$3：" + s1);
                    System.out.println("sum3$3：" + s2);
                    return s1 + "$" + s2 + "$";
                }
            });
        System.out.println(result);
        return result;
    }
}
